''''''

# Standard
import logging
import warnings
# Related
import lx
# Local
import moopy.al.command.symmetry
import moopy.al.query.mesh_element
import moopy.al.query.symmetry


logger = logging.getLogger('moopy')

class MeshElement(object):
    '''The base mesh element class.'''

    def __init__(self, element_index, mesh_item, *args, **kwargs):
        '''
        '''
        super(MeshElement, self).__init__(*args, **kwargs)

        #: The index of this element.
        self.index = element_index

        #: The mesh which contains this element.
        self.mesh = mesh_item

    def __eq__(self, other):
        ''''''
        if isinstance(other, MeshElement):
            return self.index == other.index
        else:
            return NotImplemented

    def __ne__(self, other):
        ''''''
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return equal
        return not equal

    def __repr__(self):
        ''''''
        return "<class 'moopy.mesh_element.MeshElement', index:%s>" % (
            self.index)

    def __str__(self):
        ''''''
        return 'Mesh Element, Index "%s"' % (self.index)

class Vertex(MeshElement):
    '''A representation of a single vertex.'''

    def __init__(self, vertex_index, mesh_item, *args, **kwargs):
        '''
        '''
        super(Vertex, self).__init__(vertex_index, mesh_item,
                                     *args, **kwargs)

    def __repr__(self):
        ''''''
        return "<class 'moopy.mesh_element.Vertex', index:%s>" % (
            self.index)

    def __str__(self):
        ''''''
        return 'Mesh Vertex, Index "%s"' % (self.index)

    def __eq__(self, other):
        ''''''
        if isinstance(other, Vertex):
            return self.index == other.index
        else:
            return NotImplemented

    def __ne__(self, other):
        ''''''
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return equal
        else:
            return not equal

    def _get_local_pos(self):
        ''''''
        return moopy.al.query.mesh_element.get_vertex_position(
            self.mesh.index, self.index)

    def _set_local_pos(self, value):
        ''''''
        raise NotImplementedError()

    local_pos = property(
        _get_local_pos,
        _set_local_pos,
        doc='''The local position of this vertex, relative to the mesh item.

        @important: If you need to get x/y/z values, this option is preferable.
        The other specific x/y/z accessors will simply pull all 3 values
        anyway, and only return the specific one in question. They're just
        shorthand for this.
        '''
    )

    def _get_local_posx(self):
        ''''''
        return self.local_pos[0]

    def _set_local_posx(self, value):
        ''''''
        raise NotImplementedError()

    local_posx = property(
        _get_local_posx,
        _set_local_posx,
        doc='''
        '''
    )

    def _get_local_posy(self):
        ''''''
        return self.local_pos[1]

    def _set_local_posy(self, value):
        ''''''
        raise NotImplementedError()

    local_posy = property(
        _get_local_posy,
        _set_local_posy,
        doc='''
        '''
    )

    def _get_local_posz(self):
        ''''''
        return self.local_pos[2]

    def _set_local_posz(self, value):
        ''''''
        raise NotImplementedError()

    local_posz = property(
        _get_local_posz,
        _set_local_posz,
        doc='''
        '''
    )

    def _get_local_rotx(self):
        ''''''
        raise NotImplementedError()

    def _set_local_rotx(self, value):
        ''''''
        raise NotImplementedError()

    local_rotx = property(
        _get_local_rotx,
        _set_local_rotx,
        doc='''
        '''
    )

    def _get_local_roty(self):
        ''''''
        raise NotImplementedError()

    def _set_local_roty(self, value):
        ''''''
        raise NotImplementedError()

    local_roty = property(
        _get_local_roty,
        _set_local_roty,
        doc='''
        '''
    )

    def _get_local_rotz(self):
        ''''''
        raise NotImplementedError()

    def _set_local_rotz(self, value):
        ''''''
        raise NotImplementedError()

    local_rotz = property(
        _get_local_rotz,
        _set_local_rotz,
        doc='''
        '''
    )

    def _get_local_sclx(self):
        ''''''
        raise NotImplementedError()

    def _set_local_sclx(self, value):
        ''''''
        raise NotImplementedError()

    local_sclx = property(
        _get_local_sclx,
        _set_local_sclx,
        doc='''
        '''
    )

    def _get_local_scly(self):
        ''''''
        raise NotImplementedError()

    def _set_local_scly(self, value):
        ''''''
        raise NotImplementedError()

    local_scly = property(
        _get_local_scly,
        _set_local_scly,
        doc='''
        '''
    )

    def _get_local_sclz(self):
        ''''''
        raise NotImplementedError()

    def _set_local_sclz(self, value):
        ''''''
        raise NotImplementedError()

    local_sclz = property(
        _get_local_sclz,
        _set_local_sclz,
        doc='''
        '''
    )

    def _get_selected(self):
        ''''''
        return moopy.al.query.mesh_element.get_vertex_selected(
            self.mesh.index, self.index)

    def _set_selected(self, value):
        ''''''
        raise NotImplementedError()

    selected = property(
        _get_selected,
        _set_selected,
        doc='''If the vertex is selected by the user.
        '''
    )

    def _get_symmetrical_vertex(self):
        ''''''
        moopy.al.query.mesh_element.get_symmetrical_vertex(
            self.index, self.mesh.index)

    symmetrical_vertex = property(
        _get_symmetrical_vertex,
        doc='''This returns the symmetrical vertex, as seen by modo. It
        requires a symmetry axis to be active. If no symmetry axis is active
        or no vertex is symmetrical, None is returned.

        @also: Note that L{self.symmetrical_vertex_x/y/z 
        <self.symmetrical_vertex_x>} are something to keep in mind. Often
        a better alternative to this function.
        '''
    )

    def _get_symmetrical_vertex_x(self):
        ''''''
        # Get the sym state, so we can toggle the value.
        current_sym_state = moopy.al.query.symmetry.get_symmetry_state()

        # Set the sym to X so we can get the correct Sym Vert.
        moopy.al.command.symmetry.set_symmetry_state('x')
        sym_vert_index = moopy.al.query.mesh_element.get_symmetrical_vertex(
            self.index, self.mesh.index)

        # Now set the sym back.
        moopy.al.command.symmetry.set_symmetry_state(current_sym_state)

        if sym_vert_index is not None:
            return Vertex(sym_vert_index, self.mesh)
        else:
            return None

    symmetrical_vertex_x = property(
        _get_symmetrical_vertex_x,
        doc='''The same as L{<self.symmetrical_vertex>} except this will
        set the symmetry axis to X, get the vertex (if any) and then return
        the symmetry axis to what it previously was.

        @important: These X/Y/Z properties can be costly, so use the wisely.
        '''
    )

    def _get_symmetrical_vertex_y(self):
        ''''''
        # Get the sym state, so we can toggle the value.
        current_sym_state = moopy.al.query.symmetry.get_symmetry_state()

        # Set the sym to Y so we can get the correct Sym Vert.
        moopy.al.command.symmetry.set_symmetry_state('y')
        sym_vert_index = moopy.al.query.mesh_element.get_symmetrical_vertex(
            self.index, self.mesh.index)

        # Now set the sym back.
        moopy.al.command.symmetry.set_symmetry_state(current_sym_state)

        if sym_vert_index is not None:
            return Vertex(sym_vert_index, self.mesh)
        else:
            return None

    symmetrical_vertex_y = property(
        _get_symmetrical_vertex_y,
        doc='''The same as L{<self.symmetrical_vertex>} except this will
        set the symmetry axis to Y, get the vertex (if any) and then return
        the symmetry axis to what it previously was.

        @important: These X/Y/Z properties can be costly, so use the wisely.
        '''
    )

    def _get_symmetrical_vertex_z(self):
        ''''''
        # Get the sym state, so we can toggle the value.
        current_sym_state = moopy.al.query.symmetry.get_symmetry_state()

        # Set the sym to Z so we can get the correct Sym Vert.
        moopy.al.command.symmetry.set_symmetry_state('z')
        sym_vert_index = moopy.al.query.mesh_element.get_symmetrical_vertex(
            self.index, self.mesh.index)

        # Now set the sym back.
        moopy.al.command.symmetry.set_symmetry_state(current_sym_state)

        if sym_vert_index is not None:
            return Vertex(sym_vert_index, self.mesh)
        else:
            return None

    symmetrical_vertex_z = property(
        _get_symmetrical_vertex_z,
        doc='''The same as L{<self.symmetrical_vertex>} except this will
        set the symmetry axis to Z, get the vertex (if any) and then return
        the symmetry axis to what it previously was.

        @important: These X/Y/Z properties can be costly, so use the wisely.
        '''
    )

    def _get_world_posx(self):
        ''''''
        raise NotImplementedError()

    def _set_world_posx(self, value):
        ''''''
        raise NotImplementedError()

    world_posx = property(
        _get_world_posx,
        _set_world_posx,
        doc='''
        '''
    )

    def _get_world_posy(self):
        ''''''
        raise NotImplementedError()

    def _set_world_posy(self, value):
        ''''''
        raise NotImplementedError()

    world_posy = property(
        _get_world_posy,
        _set_world_posy,
        doc='''
        '''
    )

    def _get_world_posz(self):
        ''''''
        raise NotImplementedError()

    def _set_world_posz(self, value):
        ''''''
        raise NotImplementedError()

    world_posz = property(
        _get_world_posz,
        _set_world_posz,
        doc='''
        '''
    )

    def _get_world_rotx(self):
        ''''''
        raise NotImplementedError()

    def _set_world_rotx(self, value):
        ''''''
        raise NotImplementedError()

    world_rotx = property(
        _get_world_rotx,
        _set_world_rotx,
        doc='''
        '''
    )

    def _get_world_roty(self):
        ''''''
        raise NotImplementedError()

    def _set_world_roty(self, value):
        ''''''
        raise NotImplementedError()

    world_roty = property(
        _get_world_roty,
        _set_world_roty,
        doc='''
        '''
    )

    def _get_world_rotz(self):
        ''''''
        raise NotImplementedError()

    def _set_world_rotz(self, value):
        ''''''
        raise NotImplementedError()

    world_rotz = property(
        _get_world_rotz,
        _set_world_rotz,
        doc='''
        '''
    )

    def _get_world_sclx(self):
        ''''''
        raise NotImplementedError()

    def _set_world_sclx(self, value):
        ''''''
        raise NotImplementedError()

    world_sclx = property(
        _get_world_sclx,
        _set_world_sclx,
        doc='''
        '''
    )

    def _get_world_scly(self):
        ''''''
        raise NotImplementedError()

    def _set_world_scly(self, value):
        ''''''
        raise NotImplementedError()

    world_scly = property(
        _get_world_scly,
        _set_world_scly,
        doc='''
        '''
    )

    def _get_world_sclz(self):
        ''''''
        raise NotImplementedError()

    def _set_world_sclz(self, value):
        ''''''
        raise NotImplementedError()

    world_sclz = property(
        _get_world_sclz,
        _set_world_sclz,
        doc='''
        '''
    )

class UVVertex(object):
    ''''''

    def __init__(self, polygon_index, vertex_index, mesh_item, uv_map):
        ''''''

        self._vertex = None

        #: The mesh object which contains this uv vert.
        self.mesh = mesh_item
        #: In tuple form, the name of this uv vert (I{this is what modo
        #: considers a uv name})
        self.name = (mesh_item.index, polygon_index, vertex_index)
        self.polygon_index = polygon_index
        #: The uv map which this uv vert belongs to.
        self.uv_map = uv_map
        self.vertex_index = vertex_index

    def __repr__(self):
        ''''''
        return "<class 'moopy.mesh_element.UVVertex', name:%s>" % (
            str(self.name))

    def __str__(self):
        ''''''
        return 'UV Vertex, Name "%s"' % str(self.name)

    def __eq__(self, other):
        ''''''
        if isinstance(other, UVVertex):
            return self.name == other.name
        else:
            return NotImplemented

    def __ne__(self, other):
        ''''''
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return equal
        return not equal

    def _get_polygons(self):
        ''''''
        raise NotImplementedError()

    polygons = property(
        _get_polygons,
        doc='''
        '''
    )

    def _get_posu(self):
        ''''''
        return self.pos[0]

    def _set_posu(self, value):
        ''''''
        raise NotImplementedError()

    posu = property(
        _get_posu,
        _set_posu,
        doc='''Get both the U and V coordinate values, but only return the
        U value.
        '''
    )

    def _get_posv(self):
        ''''''
        return self.pos[1]

    def _set_posv(self, value):
        ''''''
        raise NotImplementedError()

    posv = property(
        _get_posv,
        _set_posv,
        doc='''Get both the U and V coordinate values, but only return the
        V value.
        '''
    )

    def _get_pos(self):
        ''''''
        return moopy.al.query.mesh_element.get_uv_position(
            self.uv_map.index, self.name)

    def _set_pos(self, value):
        ''''''
        raise NotImplementedError()

    pos = property(
        _get_pos,
        _set_pos,
        doc='''The same as L{<self.posu>} and L{<self.posv>} except this
        returns both U and V in the form of a tuple. If both are needed,
        it is advised to use this function over using both L{<self.posu>} and
        L{<self.posv>} given that they B{I{each}} make the same calls to modo
        that this does, but only return a single value.
        '''
    )

    def _get_vertex(self):
        ''''''
        if self._vertex is None:
            self._vertex = Vertex(self.vertex_index, self.mesh)
            return self._vertex
        else:
            return self._vertex

    vertex = property(
        _get_vertex,
        doc='''
        '''
    )
    
    @classmethod
    def new(cls, mesh):
        ''''''
        raise NotImplementedError()
